#pragma once

#include <cstdint>
#include <string>
#include <cassert>
#include <list>
#include <vector>
#include <stack>
#include <algorithm>
#include <unordered_map>
#include <sstream>
#include <iomanip>
#include <memory>

#define		CYB_KB( n ) ( n * 1024L )
#define		CYB_MB( n ) ( CYB_KB( n ) * 1024L )
#define		CYB_GB( n ) ( CYB_MB( n ) * 1024L )

namespace cyb
{

#define INTERNAL_REFS 2
template <class T>
class ResourceCache
{
public:
	using CacheMap = std::unordered_map<std::string, std::shared_ptr<T>>;
	using CacheList = std::list<std::shared_ptr<T>>;

	ResourceCache( const std::string& name)	: m_name( name ) { static_assert( std::is_base_of<Resource, T>::value, "T must inherit from Resource" ); }
	~ResourceCache() = default;

	std::shared_ptr<T> Add( const std::string& name )
	{
		auto entry = m_resources.find( name );
		if ( entry != m_resources.end() )
		{
			return Update( entry->second );
		}

		auto resource = std::make_shared<T>( name );
		if ( !resource->Load() ) 
		{
			return nullptr;
		}

		m_resources.insert( std::pair<std::string, std::shared_ptr<T>>( name, resource ) );
		m_lru.push_front( resource );
		return resource;
	}

	// UnLoad and remove the resource from the cache, however, the resource may 
	// still exist due to the nature of shared_ptr.
	bool Remove( const std::string& name )
	{
		auto entry = m_resources.find( name );
		if ( entry != m_resources.end() )
		{
			entry->UnLoad();
			auto resource = entry->second;
			m_resources.erase( entry );
			m_lru.erase( resource );
			return true;
		}

		return false;
	}

	void Remove( const std::shared_ptr<T> resource )
	{
		Remove( resource->ResourceName() );
	}

	void RemoveUnused()
	{
		auto iter = m_lru.begin();
		while  ( iter != m_lru.end() )
		{
			if ( (*iter).use_count() <= INTERNAL_REFS )
			{
				m_resources.erase( (*iter)->ResourceName() );
				m_lru.erase( iter++ );
			}
			else
			{
				++iter;
			}
		}
	}

	std::shared_ptr<T> Update( std::shared_ptr<T> entry )
	{
		m_lru.remove( entry );
		m_lru.push_front( entry );
		return entry;
	}

	const CacheList& LRUList() const { return m_lru; }

	const std::string UsageString()
	{
		std::ostringstream stream;
		stream << "Usage for cache: " << m_name << std::endl;
		stream << std::setw( 35 ) << "Resource name," << std::setw( 12 ) << "References," << std::setw( 10 ) << "Size (kb)" << std::endl;
		stream << "---------------------------------------------------------";
		for ( auto& resource : m_lru )
		{
			stream << std::endl;
			stream << std::setw( 34 ) << resource->ResourceName() << std::setw( 12 ) << resource.use_count()-INTERNAL_REFS << std::setw( 10 ) << std::ceil( resource->AllocatedSize()/1024.0f );
		}

		return stream.str();
	}

private:
	std::string m_name;
	CacheMap m_resources;
	CacheList m_lru;	// least recently used
};
#undef INTERNAL_REFS

}	// namespace cyb